package com.xsf.pay.common.filter;


import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xsf.pay.common.domain.Rc;
import com.xsf.pay.common.exception.BusinessRunTimeException;
import com.xsf.pay.common.utils.RequestUtil;
import com.xsf.pay.common.utils.StringUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * @author huanglei
 */
@Slf4j
public class AccessRequestWrapper extends HttpServletRequestWrapper {
    private byte[] body;

    /**
     * 是否是文件上传
     */
    private boolean fileUpload = false;

    /**
     * xss脚本正则
     */
    private final static Pattern[] scriptPatterns = {
            Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<html>(.*?)</html>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("src[\r\n]*=[\r\n]*'(.*?)'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    public AccessRequestWrapper(HttpServletRequest request) {
        super(request);
        body = RequestUtil.getBodyString(request).getBytes(StandardCharsets.UTF_8);
        Rc rc = new Rc(request);
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(rc);
//        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(rc.getSession().getServletContext());
//        boolean isMultipart = commonsMultipartResolver.isMultipart(request);
//        if (isMultipartContent || isMultipart) {
        if (isMultipartContent) {
            fileUpload = true;
        }
    }

    /**
     * 过滤springmvc中的 @RequestParam 注解中的参数
     *
     * @param s 参数
     * @return 过滤后结果
     */
    @Override
    public String[] getParameterValues(String s) {
        String[] str = super.getParameterValues(s);
        if (str == null) {
            return null;
        }
        int i = str.length;
        String[] as1 = new String[i];
        for (int j = 0; j < i; j++) {
            as1[j] = cleanXSS(cleanSQLInject(str[j]));
        }
        log.info("XssHttpServletRequestWrapper净化后的请求为：========== {}", Arrays.toString(as1));
        return as1;
    }

    /**
     * 过滤request.getParameter的参数
     *
     * @param s 参数
     * @return 获取结果
     */
    @Override
    public String getParameter(String s) {
        String s1 = super.getParameter(s);
        if (s1 == null) {
            return null;
        } else {
            String s2 = cleanXSS(cleanSQLInject(s1));
            log.info("XssHttpServletRequestWrapper净化后的请求为：========== {}", s2);
            return s2;
        }
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {
        // 非文件上传进行过滤
        if (!fileUpload) {
            // 获取body中的请求参数
            JSONObject json = JSONObject.parseObject(new String(body));
            // 校验并过滤xss攻击和sql注入
            checkJson(json);
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() {
                return bais.read();
            }
        };
    }

    private void checkJson(JSONObject jsonObject) {
        if(ObjectUtil.isNull(jsonObject)){
            return;
        }
        // 校验并过滤xss攻击和sql注入
        for (String k : jsonObject.keySet()) {
            if (jsonObject.get(k) instanceof JSONObject) {
                checkJson(jsonObject.getJSONObject(k));
            } else if (jsonObject.get(k) instanceof JSONArray) {
                jsonObject.getJSONArray(k).forEach(this::validParam);
            } else {
                cleanSQLInject(cleanXSS(jsonObject.getString(k)));
            }
        }
    }

    private void validParam(Object item) {
        if (item instanceof JSONObject) {
            checkJson((JSONObject) item);
        } else if (item instanceof JSONArray) {
            ((JSONArray)item).forEach(this::validParam);
        } else {
            cleanSQLInject(cleanXSS(item.toString()));
        }
    }

    /**
     * 清除xss
     *
     * @param src 单个参数
     * @return 返回结果
     */
    public String cleanXSS(String src) {
        if (StringUtil.isEmpty(src)) {
            return "";
        }
        String temp = src;
        // 校验xss脚本
        for (Pattern pattern : scriptPatterns) {
            temp = pattern.matcher(temp).replaceAll("");
        }
        // 校验xss特殊字符
//        temp = temp.replaceAll("[\u0000\n\r]", "");
//        temp = temp.replaceAll("<", "&lt;").replaceAll(">", "&gt;");

        if (!temp.equals(src)) {

            log.error("xss攻击检查：参数含有非法攻击字符，已禁止继续访问！！");
            log.error("原始输入信息-->" + temp);

            throw new BusinessRunTimeException(1001, "参数含有非法字符");
        }

        return src;
    }

    /**
     * 过滤sql注入 -- 需要增加通配，过滤大小写组合
     *
     * @param src 单个参数值
     * @return 返回结果
     */
    public String cleanSQLInject(String src) {
        log.info("XssHttpServletRequestWrapper过滤sql注入的参数为：========== {}", src);
        /**
         * sql注入正则
         */
        String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|/\\*.+?\\*/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
        Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);
        if (sqlPattern.matcher(src.toLowerCase()).find()) {
            log.error("sql注入检查：输入信息存在SQL攻击！");
            throw new BusinessRunTimeException(1001, "参数含有非法攻击字符");
        }
        return src;
    }

    /**
     * 手动设置新的请求体内容（字符串形式）
     */
    public void setBody(String content) {
        this.body = content.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 手动设置新的请求体内容（字节数组形式）
     */
    public void setBody(byte[] content) {
        this.body = Arrays.copyOf(content, content.length);
    }
}
